﻿using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Navigation;
using System.Windows.Threading;

namespace Microsoft.Phone.Controls
{
    /// <summary>
    /// Alternative <see cref="PhoneApplicationPage">PhoneApplicationPage</see> implementation. Can be used for cases when quick Back-key pressing causes exception because page is not loaded.
    /// </summary>
    public class ApplicationPage : PhoneApplicationPage
    {
        #region Logical Timers Constants

        /// <summary>
        /// Helper constants.
        /// </summary>
        protected static class TimerValues
        {
            /// <summary>
            /// Gets Back-key Timer minimum value.
            /// </summary>
            public static int BackKeyTimerMinValue
            {
                get { return 1; }
            }

            /// <summary>
            /// Gets Back-key Timer default value.
            /// </summary>
            public static int BackKeyTimerDefaultValue
            {
                get { return 1; }
            }

            /// <summary>
            /// Gets Back-key Timer maximum value.
            /// </summary>
            public static int BackKeyTimerMaxValue
            {
                get { return 5; }
            }

            /// <summary>
            /// Gets Loaded Timer minimum value.
            /// </summary>
            public static int PageLoadedTimerMinValue
            {
                get { return 1; }
            }

            /// <summary>
            /// Gets Loaded Timer default value.
            /// </summary>
            public static int PageLoadedTimerDefaultValue
            {
                get { return 1; }
            }

            /// <summary>
            /// Gets Loaded Timer maximum value.
            /// </summary>
            public static int PageLoadedTimerMaxValue
            {
                get { return 5; }
            }

            internal static int GetValidBackKeyTimerValue(int value)
            {
                return (value < BackKeyTimerMinValue)
                           ? BackKeyTimerMinValue
                           : (value > BackKeyTimerMaxValue) ? BackKeyTimerMaxValue : value;
            }

            internal static int GetValidPageLoadedTimerValue(int value)
            {
                return (value < PageLoadedTimerMinValue)
                           ? PageLoadedTimerMinValue
                           : (value > PageLoadedTimerMaxValue) ? PageLoadedTimerMaxValue : value;
            }
        }

        #endregion

        private readonly DispatcherTimer _backKeyTimer = new DispatcherTimer();
        private readonly DispatcherTimer _pageLoadedTimer = new DispatcherTimer();
        
        private Boolean _needBackKeyTimer;

        /// <summary>
        /// Occures during navigation. 
        /// </summary>
        public event EventHandler<NavigationEventArgs> NavigatedFrom;

        /// <summary>
        /// <see cref="DependencyProperty">Dependency Property</see>.
        /// </summary>
        public static readonly DependencyProperty BackKeyTimerIntervalProperty =
            DependencyProperty.Register("BackKeyTimerInterval",
                                        typeof (int),
                                        typeof (ApplicationPage),
                                        new PropertyMetadata(TimerValues.BackKeyTimerDefaultValue));

        /// <summary>
        /// <see cref="DependencyProperty">Dependency Property</see>.
        /// </summary>
        public static readonly DependencyProperty PageLoadedTimerIntervalProperty =
            DependencyProperty.Register("PageLoadedTimerInterval",
                                        typeof(int),
                                        typeof(ApplicationPage),
                                        new PropertyMetadata(TimerValues.PageLoadedTimerDefaultValue));

        /// <summary>
        /// Gets or sets Back-key press Timer interval which used for blocking Back-key logic instructions.
        /// </summary>
        public int BackKeyTimerInterval
        {
            get { return (int) GetValue(BackKeyTimerIntervalProperty); }
            set
            {
                var val = TimerValues.GetValidBackKeyTimerValue(value);

                SetValue(BackKeyTimerIntervalProperty, val);
            }
        }

        /// <summary>
        /// Gets or sets Timer interval which used for blocking instructions to give a chance for page to complete UI loading.
        /// </summary>
        public int PageLoadedTimerInterval
        {
            get { return (int)GetValue(PageLoadedTimerIntervalProperty); }
            set
            {
                var val = TimerValues.GetValidPageLoadedTimerValue(value);

                SetValue(PageLoadedTimerIntervalProperty, val);
            }
        }

        /// <summary>
        /// Called when a page becomes the active page in a frame.
        /// </summary>
        /// <param name="e">An object that contains the event data.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            _needBackKeyTimer = true;
            _backKeyTimer.Interval = new TimeSpan(0, 0, BackKeyTimerInterval);
            _pageLoadedTimer.Interval = new TimeSpan(0, 0, PageLoadedTimerInterval);
            _backKeyTimer.Tick += ProcessBackKey;
            _pageLoadedTimer.Tick += StopPageLoadedTimer;

            base.OnNavigatedTo(e);

            _pageLoadedTimer.Start();
        }

        /// <summary>
        /// Called when a page is no longer the active page in a frame.
        /// </summary>
        /// <param name="e">An object that contains the event data.</param>
        protected override void OnNavigatedFrom(NavigationEventArgs e)
        {
            base.OnNavigatedFrom(e);

            if (NavigatedFrom != null)
            {
                NavigatedFrom(this, e);
            }

            if (_backKeyTimer.IsEnabled)
            {
                _backKeyTimer.Stop();
            }

            if (_pageLoadedTimer.IsEnabled)
            {
                _pageLoadedTimer.Stop();
            }

            _backKeyTimer.Tick -= ProcessBackKey;
            _pageLoadedTimer.Tick -= StopPageLoadedTimer;
        }

        /// <summary>
        /// This method is called when the hardware Back button is pressed.
        /// </summary>
        /// <param name="e">Set e.Cancel to true to indicate that the request was handled by the application.</param>
        protected override void OnBackKeyPress(CancelEventArgs e)
        {
            base.OnBackKeyPress(e);

            if (!NavigationService.CanGoBack)
            {
                return;
            }

            e.Cancel = true;

            if (_needBackKeyTimer)
            {
                _backKeyTimer.Start();
            }
            else
            {
                NavigationService.GoBack();
            }
        }

        private void ProcessBackKey(object sender, EventArgs e)
        {
            _backKeyTimer.Stop();

            try
            {
                if (!NavigationService.CanGoBack)
                {
                    return;
                }
                NavigationService.GoBack();
            }
            catch(Exception ex)
            {
                throw new InvalidOperationException(ex.ToString(), ex);
            }
        }

        private void StopPageLoadedTimer(object sender, EventArgs e)
        {
            _pageLoadedTimer.Stop();
            _needBackKeyTimer = false;
        }
    }
}
